package net.lanelife.casino.caipiao.service;

import java.util.Arrays;
import java.util.List;

import javax.persistence.LockModeType;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import net.lanelife.casino.caipiao.entity.QuotaRecord;
import net.lanelife.casino.caipiao.entity.RebatePointLevel;
import net.lanelife.casino.caipiao.entity.User;
import net.lanelife.casino.caipiao.entity.UserLayer;
import net.lanelife.casino.caipiao.entity.User_;
import net.lanelife.casino.caipiao.exception.DefaultUserLayerNotFoundException;
import net.lanelife.casino.caipiao.exception.ParentAccountLockedException;
import net.lanelife.casino.caipiao.exception.ParentIsNotAgentException;
import net.lanelife.casino.caipiao.exception.ParentIsNotManagerException;
import net.lanelife.casino.caipiao.exception.ParentNotExistsException;
import net.lanelife.casino.caipiao.exception.ParentRebatePointQuotaNotEnoughException;
import net.lanelife.casino.caipiao.exception.ParentRebatePointQuotaNotFoundException;
import net.lanelife.casino.caipiao.exception.RebatePointGreaterThanMaxException;
import net.lanelife.casino.caipiao.exception.RebatePointGreaterThanParentException;
import net.lanelife.casino.caipiao.exception.RebatePointLessThanChildException;
import net.lanelife.casino.caipiao.exception.RebatePointLessThanMinException;
import net.lanelife.casino.caipiao.exception.RebatePointLevelNotExistsException;
import net.lanelife.casino.caipiao.exception.SpecifiedUserLayerNotFoundException;
import net.lanelife.casino.caipiao.exception.UserExistsException;
import net.lanelife.casino.caipiao.exception.UserInformationReservationException;
import net.lanelife.casino.caipiao.exception.UserNotFoundException;
import net.lanelife.casino.caipiao.repository.api.IUserRepository;
import net.lanelife.casino.caipiao.service.api.IPreferenceService;
import net.lanelife.casino.caipiao.service.api.IQuotaRecordService;
import net.lanelife.casino.caipiao.service.api.IRebatePointLevelService;
import net.lanelife.casino.caipiao.service.api.ITeamStatisticsService;
import net.lanelife.casino.caipiao.service.api.IUserLayerService;
import net.lanelife.casino.caipiao.service.api.IUserService;
import net.lanelife.casino.caipiao.service.api.IUserStatisticsService;
import net.lanelife.casino.caipiao.utils.RebatePointUtils;
import net.lanelife.casino.common.dto.support.IGenericDTO;
import net.lanelife.casino.common.repository.AggregateExpression;
import net.lanelife.casino.common.repository.DynamicSpecifications;
import net.lanelife.casino.common.repository.SearchFilter;
import net.lanelife.casino.common.repository.support.GenericRepository;
import net.lanelife.casino.common.service.support.GenericService;
import net.lanelife.casino.common.utils.BeanMapper;
import net.lanelife.casino.common.utils.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;

@Service
public class UserService extends GenericService<User, Integer> implements IUserService {

	@Autowired
	private IUserRepository repository;
	@Autowired
	private ITeamStatisticsService teamStatisticsService;
	@Autowired
	private IUserStatisticsService userStatisticsService;
	@Autowired
	private IRebatePointLevelService rebatePointLevelService;
	@Autowired
	private IPreferenceService preferenceService;
	@Autowired
	private IUserLayerService userLayerService;
	@Autowired
	private IQuotaRecordService quotaRecordService;
	
	public UserService() {
		super(User.class);
	}
	
	@Override
	protected GenericRepository<User, Integer> getRepository() {
		return repository;
	}
	
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public User findByUserName(String userName) {
		return repository.findByUserName(userName);
	}
	
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public String getPath(String userName) {
		User user = repository.findByUserName(userName);
		if (user != null) return user.getPath();
		return null;
	}

	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<User> findParentsByPath(String path) {
		List<Integer> ids = StringUtils.toIntegerList(path, "\\|");
		ids.remove(ids.size() - 1);
		return repository.findAll(ids);
	}
	
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Boolean exists(String userName) {
		return null != repository.findByUserName(userName);
	}
	
	@Override
	@Transactional(rollbackFor = Exception.class)
	public <DTO extends IGenericDTO<Integer>> void save(IGenericDTO<Integer> dto) throws Exception {
		
		String reserv                         = preferenceService.find("site.user.information.reservation.keyword").stringValue();		// 用户信息保留关键字
		double minPoint                       = preferenceService.find("rebate.point.min").doubleValue();								// 最低返点
		double maxPoint                       = preferenceService.find("rebate.point.max").doubleValue();								// 最高返点
		double oriPoint                       = 0D;																						// 用户原返点
		double curPoint                       = 0D;																						// 用户新返点
		User user                             = null;																					// 用户
		String path                           = "";																						// 路径
		Integer floor                         = 1;																						// 层级
		UserLayer layer                       = null;																					// 用户分层
		User parent                           = null;																					// 上级
		List<RebatePointLevel> pointLevelList = null;																					// 返点级别列表
		RebatePointLevel oriPointLevel        = null;																		        	// 原返点级别
		RebatePointLevel curPointLevel        = null;																		        	// 新返点级别
		String userOriQuota                   = null;																					// 用户原始配额
		List<User.Quota> userLossQuota        = null;																					// 用户丢失的配额，丢失原因：返点级别变更、降点
		Integer parentInQuota                 = 0;																						// 需返还上级的配额
		Integer parentOutQuota                = 0;																						// 需扣除上级的配额
		String parentOriQuota                 = null;																					// 上级原始配额信息
		List<User.Quota> parentLossQuota      = null;																					// 上级丢失的配额，丢失原因：返点级别变更

		
		user = BeanMapper.map(dto, entityClass);
		
		if (user.isNew()) {
			// 检查用户帐号是否存在
			if (this.exists(user.getUserName())) {
				throw UserExistsException.ERROR;																			// 异常：用户名已存在
			}
			oriPoint = curPoint = user.getRebatePoint();
		} else {
			// 查询用户并锁定
			user = repository.findOne(user.getId(), LockModeType.PESSIMISTIC_WRITE); 
			if (user == null) {
				throw UserNotFoundException.ERROR;																			// 异常：所修改的用户不存在
			}
			oriPoint = user.getRebatePoint();
			BeanMapper.map(dto, user);
			curPoint = user.getRebatePoint();
		}
		
		// 验证用户信息保留关键字
		if (!user.validateInformation(reserv)) {
			throw UserInformationReservationException.ERROR;																// 异常：用户信息不合法
		}
		// 验证系统最低返点
		if (curPoint < minPoint) {
			throw RebatePointLessThanMinException.ERROR;																	// 异常：用户返点低于系统最低返点
		}
		// 验证系统最高返点
		if (curPoint > maxPoint) {
			throw RebatePointGreaterThanMaxException.ERROR;																	// 异常：用户返点高于系统最高返点
		}
		
		// 检查与设置用户分层
		if (user.getLayerId() == null) {
			layer = userLayerService.findDefault(); 			// 如果未指定分层，则查找默认分层
			if (layer == null) {
				throw DefaultUserLayerNotFoundException.ERROR;																// 异常：默认分层不存在
			}
		} else {
			layer = userLayerService.find(user.getLayerId()); 	// 如果已指定分层，则验证分层是否存在
			if (layer == null) {
				throw SpecifiedUserLayerNotFoundException.ERROR;															// 异常：指定分层不存在
			}
		}
		user.setLayerId(layer.getId());
		
		
		pointLevelList = rebatePointLevelService.findAll();					 // 返点级别列表
		oriPointLevel = RebatePointUtils.getLevel(pointLevelList, oriPoint); // 修改前返点级别
		curPointLevel = RebatePointUtils.getLevel(pointLevelList, curPoint); // 修改后返点级别
		
		// 检查返点级别是否存在
		if (oriPointLevel == null || curPointLevel == null) {
			throw RebatePointLevelNotExistsException.ERROR;																	// 异常：返点级别不存在
		}
		
		// 用户配额操作 ======================================== BEGIN ========================================
		userOriQuota = user.getQuota();
		user.setQuota(RebatePointUtils.generateQuota(pointLevelList, userOriQuota, oriPoint)); // 按原返点生成配额，用于判断返点级别是否调整
		userLossQuota = RebatePointUtils.compareQuota(user.getQuota(), userOriQuota); 
		for (User.Quota q : userLossQuota) {
			if (q.getQuota() > 0) {
				// 系统回收  返点级别变更（无法返还给上级）
				saveQuotaRecord(user, QuotaRecord.Direction.OUT, QuotaRecord.Type.OUT_SYSTEM_RECY, q.getBeginRebatePoint(), q.getEndRebatePoint(), q.getQuota(), "");
			}
		}
		
		userOriQuota = user.getQuota();
		user.setQuota(RebatePointUtils.generateQuota(pointLevelList, userOriQuota, curPoint)); // 按新返点生成配额，用于判断是否降点
		userLossQuota = RebatePointUtils.compareQuota(user.getQuota(), userOriQuota);
		for (User.Quota q : userLossQuota) {
			if (q.getQuota() > 0) {
				// 上级回收或系统回收
				saveQuotaRecord(user, QuotaRecord.Direction.OUT, user.hasParent() ? QuotaRecord.Type.OUT_PARENT_RECY : QuotaRecord.Type.OUT_SYSTEM_RECY, q.getBeginRebatePoint(), q.getEndRebatePoint(), q.getQuota(), "");
			}
		}
		// 用户配额操作 ======================================== END ========================================
		
		if (user.hasParent()) {
			// 查询上级并锁定
			parent = repository.findOne(DynamicSpecifications.<User>bySearchFilter(Arrays.asList(new SearchFilter(User_.userName.getName(), SearchFilter.Operator.EQ, user.getParentName()))), LockModeType.PESSIMISTIC_WRITE);
			if (parent == null) {
				throw ParentNotExistsException.ERROR;																		// 异常：上级帐号不存在
			}
			// 如果上级非管理帐号则禁止开管理帐号或将已有帐号变更为管理帐号
			if (user.getAccountType() == User.AccountType.MANAGER && parent.getAccountType() != User.AccountType.MANAGER) {
				throw ParentIsNotManagerException.ERROR;																	// 异常：上级非管理帐号
			}
			// 检查用户返点是否大于上级返点
			maxPoint = parent.getRebatePoint();
			if (curPoint > maxPoint) {
				throw RebatePointGreaterThanParentException.ERROR;															// 异常：用户返点高于上级返点
			}
			
			// 上级配额操作 ======================================== BEGIN ========================================
			parentOriQuota = parent.getQuota();
			
			parent.setQuota(RebatePointUtils.generateQuota(pointLevelList, parentOriQuota, parent.getRebatePoint())); // 生成新配额信息
			
			int remainingQuota = RebatePointUtils.getQuota(parent.getQuota(), curPoint); // 上级剩余配额
			
			if (remainingQuota == -1) {
				throw ParentRebatePointQuotaNotFoundException.ERROR;														// 异常：上级返点配额不存在
			}
			
			// 计算进出配额
			if (user.isNew()) {
				parentOutQuota = curPointLevel.getQuotaPrice();
			} else {
				if (oriPointLevel.getId() != curPointLevel.getId()) { // 不同返点级别才需要返还和扣除配额
					parentInQuota = user.getExpendQuota();
					parentOutQuota = curPointLevel.getQuotaPrice();
				}
			}
			
			// 验证剩余配额
			if (remainingQuota < parentOutQuota) {
				throw ParentRebatePointQuotaNotEnoughException.ERROR;														// 异常：上级返点配额不足
			}
			
			if (parentInQuota > 0) {  // 返还配额
				parent.setQuota(RebatePointUtils.changeQuota(parent.getQuota(), oriPoint, parentInQuota, RebatePointUtils.DIRECTION_IN));
				saveQuotaRecord(parent, QuotaRecord.Direction.IN, QuotaRecord.Type.IN_QUOTA, oriPointLevel.getBeginRebatePoint(), oriPointLevel.getEndRebatePoint(), parentInQuota.longValue(), user.getUserName());
			}
			if (parentOutQuota > 0) { // 扣除配额
				parent.setQuota(RebatePointUtils.changeQuota(parent.getQuota(), curPoint, parentOutQuota, RebatePointUtils.DIRECTION_OUT));
				saveQuotaRecord(parent, QuotaRecord.Direction.OUT, QuotaRecord.Type.OUT_QUOTA, curPointLevel.getBeginRebatePoint(), curPointLevel.getEndRebatePoint(), parentOutQuota.longValue(), user.getUserName());
			}
			user.setExpendQuota(parentOutQuota);
			
			// 因系统返点级别调整，系统回收上级丢失的配额
			parentLossQuota = RebatePointUtils.compareQuota(parent.getQuota(), parentOriQuota);
			for (User.Quota q : parentLossQuota) {
				if (q.getQuota() > 0) {
					saveQuotaRecord(parent, QuotaRecord.Direction.OUT, QuotaRecord.Type.OUT_SYSTEM_RECY, q.getBeginRebatePoint(), q.getEndRebatePoint(), q.getQuota(), "");
				}
			}
			
			// 将用户降点后丢失的配额返还给上级
			for (User.Quota q : userLossQuota) {
				if (q.getQuota() > 0) {
					parent.setQuota(RebatePointUtils.changeQuota(parent.getQuota(), q.getBeginRebatePoint(), q.getQuota().intValue(), RebatePointUtils.DIRECTION_IN));
					saveQuotaRecord(parent, QuotaRecord.Direction.IN, QuotaRecord.Type.IN_PARENT_RECY, q.getBeginRebatePoint(), q.getEndRebatePoint(), q.getQuota(), user.getUserName());
				}
			}
			
			// 上级配额操作 ======================================== END ========================================
			
		}
		
		// 查询直接下级最高返点并锁定直接下级
		minPoint = repository.aggregate(Double.class, DynamicSpecifications.<User>bySearchFilter(Arrays.asList(new SearchFilter(User_.parentName.getName(), SearchFilter.Operator.EQ, user.getUserName()))), new AggregateExpression<User>() {
			@Override
			public List<Selection<?>> buildExpression(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
				List<Selection<?>> columns = Lists.newArrayList();
				columns.add(builder.coalesce(builder.max(root.get(User_.rebatePoint)), 0D));
				return columns;
			}
		}, LockModeType.PESSIMISTIC_WRITE);
		
		// 检查用户返点是否小于下级返点
		if (curPoint < minPoint) {
			throw RebatePointLessThanChildException.ERROR;																	// 异常：用户返点低于下级返点
		}
		
		
		if (user.isNew()) {
			if (parent != null) {
				// 非管理或代理帐号禁止开下级帐号
				if (parent.getAccountType() != User.AccountType.MANAGER && parent.getAccountType() != User.AccountType.AGENT) {
					throw ParentIsNotAgentException.ERROR;																	// 异常：上级非管理或代理帐号
				}
				// 验证上级帐号锁定状态
				if (parent.getAccountLocked()) {
					throw ParentAccountLockedException.ERROR;																// 异常：上级帐号已禁用
				}
				// 关联上级、设置路径与层级
				user.setParentId(parent.getId());
				user.setParentName(parent.getUserName());
				path = parent.getPath();
				floor += parent.getFloor();
			}
			user.setFloor(floor);
			repository.save(user);                   //新增用户
			user.setPath(path + user.getId() + "|"); //更新路径
			// 生成团队统计表
			teamStatisticsService.create(user);
			// 生成用户统计表
			userStatisticsService.create(user);
		}
		
				
	}
	
	
	/**
	 * 写入配额收支记录
	 */
	private void saveQuotaRecord(User user, QuotaRecord.Direction dir, QuotaRecord.Type type, Double begin, Double end, Long quota, String childName) throws Exception {
		QuotaRecord qr = new QuotaRecord(user);
		qr.setDirection(dir);
		qr.setType(type);
		qr.setBeginRebatePoint(begin);
		qr.setEndRebatePoint(end);
		qr.setQuota(quota);
		qr.setChildName(childName);
		quotaRecordService.save(qr);
	}

	
	

}
